﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using System.Data;
using FinalProject.Data.DBInteractions.Interfaces;
using System.Linq.Expressions;
using FinalProject.Data.Search;

namespace FinalProject.Data.DBInteractions
{
    public abstract class EntityRepositoryBase<T> : IEntityRepository<T> where T : class
    {
        private FinalProjectContext _dataContext;
        private readonly IDbSet<T> dbset;
        protected EntityRepositoryBase(IDBFactory databaseFactory)
        {
            DatabaseFactory = databaseFactory;
            dbset = DataContext.Set<T>();
        }

        protected IDBFactory DatabaseFactory
        {
            get;
            private set;
        }

        protected FinalProjectContext DataContext
        {
            get { return _dataContext ?? (_dataContext = DatabaseFactory.Get()); }
        }

        public virtual void Add(T entity)
        {
            dbset.Add(entity);
        }
        public virtual void Update(T entity)
        {
            _dataContext.Entry(entity).State = EntityState.Modified;
        }
        public virtual void Delete(T entity)
        {
            dbset.Remove(entity);
        }
        public void Delete(Func<T, Boolean> where)
        {
            IEnumerable<T> objects = dbset.Where<T>(where).AsEnumerable();
            foreach (T obj in objects)
                dbset.Remove(obj);
        }

        public PagedListResult<T> Search(SearchQuery<T> searchQuery)
        {
            IQueryable<T> sequence = dbset;

            sequence = ManageFilters(searchQuery, sequence);
            sequence = ManageIncludeProperties(searchQuery, sequence);
            sequence = ManageSortCriterias(searchQuery, sequence);

            return GetTheResult(searchQuery, sequence);
        }

        public PagedListResult<T> GetTheResult(SearchQuery<T> searchQuery, IQueryable<T> sequence)
        {
            var resultCount = sequence.Count();
            var result = (searchQuery.Take > 0)
                ? (sequence.Skip(searchQuery.Skip).Take(searchQuery.Take).ToList())
                : (sequence.ToList());

            bool hasNext = (searchQuery.Skip <= 0 && searchQuery.Take <= 0) ? false : (searchQuery.Skip + searchQuery.Take < resultCount);

            return new PagedListResult<T>()
            {
                Entities = result,
                HasNext = hasNext,
                HasPrevious = (searchQuery.Skip > 0),
                Count = resultCount
            };
        }

        public IQueryable<T> ManageSortCriterias(SearchQuery<T> searchQuery, IQueryable<T> sequence)
        {
            if (searchQuery.SortCriterias != null && searchQuery.SortCriterias.Count > 0)
            {
                var sortCriteria = searchQuery.SortCriterias[0];
                var orderedSequence = sortCriteria.ApplyOrdering(sequence, false);
                if (searchQuery.SortCriterias.Count > 1)
                {
                    for (var i = 1; i < searchQuery.SortCriterias.Count; i++)
                    {
                        var src = searchQuery.SortCriterias[i];
                        orderedSequence = src.ApplyOrdering(orderedSequence, true);
                    }
                }
                sequence = orderedSequence;
            }
            else
            {
                sequence = ((IOrderedQueryable<T>)sequence).OrderBy(x => (true));
            }
            return sequence;
        }

        public IQueryable<T> ManageFilters(SearchQuery<T> searchQuery, IQueryable<T> sequence)
        {
            if (searchQuery.Filters != null && searchQuery.Filters.Count > 0)
            {
                foreach (var filterClause in searchQuery.Filters)
                {
                    sequence = sequence.Where(filterClause);
                }
            }
            return sequence;
        }

        public IQueryable<T> ManageIncludeProperties(SearchQuery<T> searchQuery, IQueryable<T> sequence)
        {
            if (!String.IsNullOrWhiteSpace(searchQuery.IncludeProperties))
            {
                var properties = searchQuery.IncludeProperties.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var includeProperty in properties)
                {
                    sequence = sequence.Include(includeProperty);
                }
            }
            return sequence;
        }

        public T GetById(long id)
        {
            return dbset.Find(id);
        }

        public T Get(Func<T, bool> where)
        {
            return dbset.Where(where).FirstOrDefault();
        }

        public void Attach(T entity)
        {
            dbset.Attach(entity);
        }
        public void LoadNavigationCollection<TElement>(T entity,
            Expression<Func<T, ICollection<TElement>>> navigationProperty)
            where TElement : class
        {
            _dataContext.Entry(entity).Collection(navigationProperty).Load();
        }
    }
}
